/*
 * a simple char device driver: globalmem without mutex
 *
 * Copyright (C) 2014 Barry Song  (baohua@kernel.org)
 *
 * Licensed under GPLv2 or later.
 */

#include <linux/module.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/cdev.h>
#include <linux/slab.h>
#include <linux/uaccess.h>
#include <linux/mm.h>
#include <linux/version.h>

#include "global_mem.h"

#define GLOBALMEM_SIZE 0x1000
#define MEM_CLEAR 0x1
#define GLOBALMEM_MAJOR 230

#define IS_PAGE_ALIGNED(x) (PAGE_ALIGN((uintptr_t)(x)) == (uintptr_t)(x))

static int globalmem_major = GLOBALMEM_MAJOR;
module_param(globalmem_major, int, S_IRUGO);

struct globalmem_dev {
	unsigned char mem[GLOBALMEM_SIZE];
	struct cdev cdev;
	struct class *dev_class;
};

struct globalmem_dev *globalmem_devp;

static inline void *ce_get_pages(int numa_node, u64 size)
{
	// return  kzalloc(size, GFP_KERNEL);

	struct page *page;
	pr_info("%s %llu  %d\r\n", __func__, size, get_order(size));

	page = alloc_pages_node(numa_node, GFP_KERNEL | __GFP_ZERO,
				get_order(size));
	if (!page)
		return NULL;

	return page_address(page);
}

static inline void ce_free_pages(void *buf, u64 size)
{
	// kfree(buf);
	// return;
	pr_info("%s %llu\r\n", __func__, size);
	free_pages((unsigned long)buf, get_order(size));
}

static int globalmem_open(struct inode *inode, struct file *filp)
{
	printk(KERN_ERR "Running Func %s\n", __FUNCTION__);
	filp->private_data = globalmem_devp;
	return 0;
}

static int globalmem_release(struct inode *inode, struct file *filp)
{
	WARN_ON(globalmem_major == 231);
	printk(KERN_ERR "Running Func %s\n", __FUNCTION__);
	return 0;
}

static long globalmem_ioctl(struct file *filp, unsigned int cmd,
			    unsigned long arg)
{
	struct globalmem_dev *dev = filp->private_data;
	printk(KERN_ERR "Running Func %s", __FUNCTION__);
	switch (cmd) {
	case MEM_CLEAR:
		memset(dev->mem, 0, GLOBALMEM_SIZE);
		printk(KERN_INFO "globalmem is set to zero\n");
		break;
	case IOCTL_SET_FD:
		break;
	default:
		return -EINVAL;
	}

	return 0;
}

static ssize_t globalmem_read(struct file *filp, char __user *buf, size_t size,
			      loff_t *ppos)
{
	unsigned long p = *ppos;
	unsigned int count = size;
	int ret = 0;
	struct globalmem_dev *dev = filp->private_data;
	printk(KERN_ERR "Running Func %s", __FUNCTION__);
	if (p >= GLOBALMEM_SIZE)
		return 0;
	if (count > GLOBALMEM_SIZE - p)
		count = GLOBALMEM_SIZE - p;

	if (copy_to_user(buf, dev->mem + p, count)) {
		ret = -EFAULT;
	} else {
		*ppos += count;
		ret = count;

		printk(KERN_INFO "read %u bytes(s) from %lu\n", count, p);
	}

	return ret;
}

static ssize_t globalmem_write(struct file *filp, const char __user *buf,
			       size_t size, loff_t *ppos)
{
	unsigned long p = *ppos;
	unsigned int count = size;
	int ret = 0;
	struct globalmem_dev *dev = filp->private_data;
	printk(KERN_ERR "Running Func %s", __FUNCTION__);
	if (p >= GLOBALMEM_SIZE)
		return 0;
	if (count > GLOBALMEM_SIZE - p)
		count = GLOBALMEM_SIZE - p;

	if (copy_from_user(dev->mem + p, buf, count))
		ret = -EFAULT;
	else {
		*ppos += count;
		ret = count;

		printk(KERN_INFO "written %u bytes(s) from %lu\n", count, p);
	}

	return ret;
}

static loff_t globalmem_llseek(struct file *filp, loff_t offset, int orig)
{
	printk(KERN_ERR "Running Func %s, orig %d", __FUNCTION__, orig);
	loff_t ret = 0;
	switch (orig) {
	case 0:
		if (offset < 0) {
			ret = -EINVAL;
			break;
		}
		if ((unsigned int)offset > GLOBALMEM_SIZE) {
			ret = -EINVAL;
			break;
		}
		filp->f_pos = (unsigned int)offset;
		ret = filp->f_pos;
		break;
	case 1:
		if ((filp->f_pos + offset) > GLOBALMEM_SIZE) {
			ret = -EINVAL;
			break;
		}
		if ((filp->f_pos + offset) < 0) {
			ret = -EINVAL;
			break;
		}
		filp->f_pos += offset;
		ret = filp->f_pos;
		break;
	default:
		ret = -EINVAL;
		break;
	}
	return ret;
}

int test_mmap(struct file *filep, struct vm_area_struct *vma)
{
	u64 tag, pfn;
	int ret = -1;
	struct globalmem_dev *dev = filep->private_data;

	pfn = virt_to_phys(dev->mem) >> PAGE_SHIFT;

	//vma->vm_flags |= VM_LOCKED | VM_DONTCOPY | VM_DONTEXPAND | VM_DONTDUMP;

	ret = remap_pfn_range(vma, vma->vm_start, pfn,
			      vma->vm_end - vma->vm_start, vma->vm_page_prot);

	return ret;
}

static const struct file_operations globalmem_fops = {
	.owner = THIS_MODULE,
	.llseek = globalmem_llseek,
	.read = globalmem_read,
	.write = globalmem_write,
	.unlocked_ioctl = globalmem_ioctl,
	.open = globalmem_open,
	.release = globalmem_release,
	.mmap = test_mmap
};

static void globalmem_setup_cdev(struct globalmem_dev *dev, int index)
{
	int err, devno = MKDEV(globalmem_major, index);
	printk(KERN_ERR "Running Func %s", __FUNCTION__);
	cdev_init(&dev->cdev, &globalmem_fops);
	dev->cdev.owner = THIS_MODULE;
	err = cdev_add(&dev->cdev, devno, 1);
	if (err)
		printk(KERN_NOTICE "Error %d adding globalmem%d", err, index);
}

#include <linux/arm-smccc.h> // 包含SMC调用的头文件
#include <asm/unistd.h>
// 定义SCMI调用的函数
static int scmi_call(struct arm_smccc_res *res, u32 scmi_call_id, u32 arg1,
		     u32 arg2, u32 arg3, u32 arg4)
{
	struct arm_smccc_res result;

	// 设置SMC调用的参数
	u64 func = (u64)scmi_call_id;
	arm_smccc_smc(func, arg1, arg2, arg3, arg4, 0, 0, 0, &result);

	// 检查SMC调用是否成功
	if (result.a0) {
		pr_err("SCMI call failed with error code: %lx\n", result.a0);
		return result.a0;
	}

	// 将结果复制到提供的res结构体中
	res->a0 = result.a0;
	res->a1 = result.a1;
	res->a2 = result.a2;
	res->a3 = result.a3;

	return 0;
}

static int __init globalmem_init(void)
{
	int ret;
	struct device *devices;

	dev_t devno = MKDEV(globalmem_major, 0);
	printk(KERN_ERR "Running Func %s", __FUNCTION__);
	if (globalmem_major)
		ret = register_chrdev_region(devno, 1, "globalmem");
	else {
		ret = alloc_chrdev_region(&devno, 0, 1, "globalmem");
		globalmem_major = MAJOR(devno);
	}

	if (ret < 0)
		return ret;

	// globalmem_devp = kzalloc(sizeof(struct globalmem_dev), GFP_KERNEL);
	globalmem_devp = ce_get_pages(0, sizeof(struct globalmem_dev));
	if (!globalmem_devp) {
		ret = -ENOMEM;
		goto fail_malloc;
	}
	pr_info("kzalloc globalmem_devp %llx %d\r\n", (u64)globalmem_devp,
		IS_PAGE_ALIGNED(globalmem_devp));

	globalmem_setup_cdev(globalmem_devp, 0);
	memcpy(globalmem_devp->mem, "init data", 10);
#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 4, 0)
	globalmem_devp->dev_class = class_create("driver_dev");
#else
	globalmem_devp->dev_class = class_create(THIS_MODULE, "driver_dev");
#endif

	if (IS_ERR(globalmem_devp->dev_class)) {
		printk("class create error\r\n");
		ret = -EBUSY;
		goto fail_malloc;
	}

	devices = device_create(globalmem_devp->dev_class, NULL,
				MKDEV(globalmem_major, 0), NULL, "globalmem");
	if (NULL == devices) {
		printk("device_create error\r\n");
		ret = -EBUSY;
		class_destroy(globalmem_devp->dev_class);
		goto fail_malloc;
	}

	if (0) {
		// 使用SCMI调用的示例
		struct arm_smccc_res res;
		u32 scmi_call_id =
			0x86000001; // 假设这是SCMI协议中定义的一个命令ID
		u32 arg1 = 1; // 第一个参数

		int ret = scmi_call(&res, scmi_call_id, arg1, 0, 0, 0);
		if (ret) {
			pr_err("SCMI call failed\n");
		} else {
			pr_info("SCMI call succeeded, result: %lx, %lx, %lx, %lx\n",
				res.a0, res.a1, res.a2, res.a3);
		}
	}
	return 0;

fail_malloc:
	unregister_chrdev_region(devno, 1);
	return ret;
}
module_init(globalmem_init);

static void __exit globalmem_exit(void)
{
	printk(KERN_ERR "Running Func %s", __FUNCTION__);

	device_destroy(globalmem_devp->dev_class,
		       MKDEV(globalmem_major, 0)); /*  卸载设备 */
	class_destroy(globalmem_devp->dev_class); /*  删除类 */

	cdev_del(&globalmem_devp->cdev);
	ce_free_pages(globalmem_devp, sizeof(struct globalmem_dev));
	// kfree(globalmem_devp);
	unregister_chrdev_region(MKDEV(globalmem_major, 0), 1);
}
module_exit(globalmem_exit);

MODULE_AUTHOR("Barry Song <baohua@kernel.org>");
MODULE_LICENSE("GPL v2");
